package main

import (
	"flag"
	"fmt"
	"go/format"
	"log"
	"os"
	"strings"

	"github.com/dave/dst"
	"github.com/dave/dst/decorator"
	"github.com/dave/dst/dstutil"
	"github.com/gookit/goutil/fsutil"

	"ast/parse"
)

var projectName = flag.String("name", "", "项目名")
var projectRoot = flag.String("root", "", "项目根路径")
var goFileRootPath = flag.String("go", "./go/", "go导出路径")
var tsFileRootPath = flag.String("ts", "./ts/", "ts导出路径")

func main() {
	flag.Parse()
	*goFileRootPath = fsutil.Realpath(*goFileRootPath)
	*tsFileRootPath = fsutil.Realpath(*tsFileRootPath)
	*projectRoot = fsutil.Realpath(*projectRoot)
	fsutil.RmIfExist(*goFileRootPath)
	fsutil.RmIfExist(*tsFileRootPath)
	project := parse.NewProject(*projectName, *projectRoot)
	project.Parse(*projectRoot)
	refMap := map[string]map[string]string{}
	for _, file := range project.DSTFiles {
		adjustAST(file, project, refMap)
	}
	writeTsExportsFile(project, refMap)
}

func writeTsExportsFile(project *parse.Project, refMap map[string]map[string]string) {
	tsBuilder := new(strings.Builder)
	tsBuilder.WriteString(`import {GoInterfaceUtil} from "./wasm/GoInterfaceUtil";
import {int, int32, int64, uint, uint32, uint64, bool, float32, float64, WASMClass, WASMObj} from "./wasm/go";
`)
	tsBuilder.WriteString(fmt.Sprintf("export namespace %s_exports{\n", project.ProjectName))
	for _, pkg := range project.Packages {
		_, subName := fsutil.SplitPath(pkg.NameInProject)
		if subName != "" {
			tsBuilder.WriteString(fmt.Sprintf("export namespace %s{\n", subName))
		}
		for _, cls := range pkg.Classes {
			outputTsFile(cls, project, refMap, tsBuilder)
		}

		for fname, f := range pkg.Functions {
			if parse.FuncCanNotExport(f) {
				continue
			}
			if strings.HasSuffix(fname, "_New") {
				continue
			}
			writeFunc(nil, pkg, project, tsBuilder, fname, f)
		}

		if subName != "" {
			tsBuilder.WriteString(fmt.Sprintf("}\n"))
		}
	}
	tsBuilder.WriteString(fmt.Sprintf("}\n"))
	path := *tsFileRootPath + project.ProjectName + "_exports.ts"
	fsutil.MkParentDir(path)
	os.WriteFile(path, []byte(tsBuilder.String()), os.ModePerm)
}

func outputTsFile(cls *parse.Class, project *parse.Project, refMap map[string]map[string]string, b *strings.Builder) {
	//添加import语句
	//importFrom := refMap[cls.NameInProject]
	//importRef := map[string][]string{}
	//for refType, from := range importFrom {
	//	importRef[from] = append(importRef[from], refType)
	//}
	//
	//类型引用
	//for from, types := range importRef {
	//	for _, tp := range types {
	//		if tp == "GoInterface" || tp == "interface{}" || tp == "map" {
	//			continue
	//		}
	//		_, isBaseType := parse.BaseGoTypeNames[tp]
	//		//如果tp是基础go类型，需要引用自base_types
	//		if isBaseType {
	//			continue
	//		}
	//		b.WriteString("import {")
	//		b.WriteString(tp)
	//		absoluteFrom := fmt.Sprintf("%s/%s", from, tp)
	//		currentPkg := cls.Package.GlobalName
	//		relativePath := getRelativePath(currentPkg, absoluteFrom)
	//		b.WriteString(fmt.Sprintf("} from %s%s%s ;\n", "\"", relativePath, "\""))
	//	}
	//}
	////函数引用
	//refExportFunNames := make([]string, 0)
	//for fname, _ := range cls.Functions {
	//	refFuncName := project.ExportedFuncNameWithClass(cls, fname, true)
	//	refExportFunNames = append(refExportFunNames, refFuncName)
	//}
	//refExportFunNames = append(refExportFunNames, project.ExportedFuncNameWithClass(cls, "New", true))
	//b.WriteString(fmt.Sprintf("import {"))
	//for _, exportFName := range refExportFunNames {
	//	b.WriteString(fmt.Sprintf("%s,", exportFName))
	//}
	//absoluteFrom := fmt.Sprintf("%s/%s", project.ProjectName, project.ProjectName)
	//currentPkg := cls.Package.GlobalName
	//relativePath := getRelativePath(currentPkg, absoluteFrom)
	//
	//b.WriteString(fmt.Sprintf("} from %s%s%s;\n", "\"", relativePath, "\""))
	//constructor
	b.WriteString(fmt.Sprintf("@WASMClass\n"))
	b.WriteString(fmt.Sprintf("export class %s extends WASMObj{\n", cls.Name))
	//b.WriteString(fmt.Sprintf("\tpublic ptr:number=0;\n"))
	b.WriteString(fmt.Sprintf("\tpublic constructor(ptr?:number){\n"))
	b.WriteString(fmt.Sprintf("\t\tsuper(ptr);\n"))
	b.WriteString(fmt.Sprintf("\t\tif (this.ptr == undefined) {\n"))
	b.WriteString(fmt.Sprintf("\t\t\tthis.ptr=%s();\n", project.ExportedFuncNameWithClass(cls, cls.Package, "New", true)))
	b.WriteString(fmt.Sprintf("\t\t}\n"))
	b.WriteString(fmt.Sprintf("\t}\n"))

	for fname, f := range cls.Functions {
		if parse.FuncCanNotExport(f) {
			continue
		}
		writeFunc(cls, cls.Package, project, b, fname, f)
	}
	b.WriteString("}\n")
}

func writeFunc(cls *parse.Class, pkg *parse.Package, project *parse.Project, b *strings.Builder, fname string, f *dst.FuncDecl) {
	if cls == nil {
		b.WriteString(fmt.Sprintf("\texport function %s(", fname))

	} else {
		b.WriteString(fmt.Sprintf("\tpublic %s(", fname))
	}
	argIndex := 0
	callBuilder := new(strings.Builder)
	for _, para := range f.Type.Params.List {
		for _, name := range para.Names {
			argIndex++
			typeInfo := parse.GetFieldTypeName(para.Type)
			typeInfo = parse.GetMappedFieldTypeName(typeInfo)
			formatName := parse.FormatName(typeInfo)
			b.WriteString(fmt.Sprintf("%s:%s,", name.Name, formatName))
			callBuilder.WriteString(parse.FuncCallFormat(typeInfo, name.Name))
		}
	}
	b.WriteString("):")
	hasRet := false
	var returnType *parse.TypeInfo
	if f.Type.Results != nil && len(f.Type.Results.List) != 0 {
		typeInfo := parse.GetFieldTypeName(f.Type.Results.List[0].Type)
		typeInfo = parse.GetMappedFieldTypeName(typeInfo)
		formatName := parse.FormatName(typeInfo)
		hasRet = true
		b.WriteString(formatName)
		returnType = typeInfo
	}
	if !hasRet {
		b.WriteString("void")
	}
	//函数调用
	b.WriteString("{\n")
	refFuncName := project.ExportedFuncNameWithClass(cls, pkg, fname, true)
	extraQ := false
	returnTypeName := ""
	if hasRet {
		returnTypeName = returnType.TypeName
	}

	if hasRet {
		if returnTypeName == "WASMObj" {
			if cls == nil {
				b.WriteString(fmt.Sprintf(`return GoInterfaceUtil.fetchInterfaceFromGo((ptr) => {
		%s(ptr,`, refFuncName))
			} else {
				b.WriteString(fmt.Sprintf(`return GoInterfaceUtil.fetchInterfaceFromGo((ptr) => {
		%s(ptr,this.ptr,`, refFuncName))
			}
		} else {
			_, ok := parse.BaseGoTypeNames[returnTypeName]
			if ok {
				if returnTypeName == "bool" {
					b.WriteString(fmt.Sprintf("\t\treturn Boolean(%s(", refFuncName))
					writeFunCallPrefix(b, cls)
					extraQ = true
				} else {
					b.WriteString(fmt.Sprintf("\t\treturn %s(", refFuncName))
					writeFunCallPrefix(b, cls)

				}
			} else {
				b.WriteString(fmt.Sprintf("\t\treturn new %s(%s(", parse.FormatName(returnType), refFuncName))
				writeFunCallPrefix(b, cls)

				extraQ = true
			}
		}
	} else {
		b.WriteString(fmt.Sprintf("\t\treturn %s(", refFuncName))
		writeFunCallPrefix(b, cls)

	}
	b.WriteString(callBuilder.String())
	if extraQ {
		b.WriteString(")")
	}
	b.WriteString(");\n")
	if hasRet && returnTypeName == "WASMObj" {
		b.WriteString("\t})\n")
	}
	b.WriteString("\t}\n")
}

func writeFunCallPrefix(b *strings.Builder, cls *parse.Class) {
	if cls != nil {
		b.WriteString("this.ptr,")
	}
}

func getRelativePath(base string, abpath string) string {
	//base "jackpot/in"
	//abpath "jackpot/in/gg"
	//abpath "jackpot/dst/kk/gg"
	//abpath "jackpot/gg"
	index := 0
	blen := len(base)
	plen := len(abpath)
	for {
		if index >= blen || index >= plen {
			break
		}
		if base[index] != abpath[index] {
			break
		}
		index++
	}
	cb := base[index:]
	cp := abpath[index:]
	// base移动到abpath根目录
	upCnt := 1 + strings.Count(cb, "/")
	if len(cb) == 0 {
		upCnt = 0
	}
	prefix := ""
	for index := 0; index < upCnt; index++ {
		prefix += "../"

	}
	ret := prefix + cp
	if strings.HasPrefix(ret, "/") {
		ret = strings.TrimPrefix(ret, "/")
		ret = "./" + ret
	}
	return ret
}

type ExportFuncInfo struct {
	Name string
	PCnt int
	RCnt int
}

func adjustAST(file *parse.File, project *parse.Project, importUsage map[string]map[string]string) {

	file.DSTFile = dstutil.Apply(file.DSTFile, func(cursor *dstutil.Cursor) bool {
		switch realNode := cursor.Node().(type) {
		case *dst.FuncDecl:
			{
				decorations := realNode.Decorations().Start
				recvName := project.GetRecvName(realNode)
				funPath := project.ExportedFuncName(recvName, realNode.Name.Name, file.Package, false)
				all := decorations.All()
				newall := make([]string, 0)
				for _, oneComment := range all {
					if !strings.Contains(oneComment, "//export") {
						newall = append(newall, oneComment)
					}
				}
				canNotExport := parse.FuncCanNotExport(realNode)
				if !canNotExport {
					extraComment := fmt.Sprintf("//export %s\n", funPath)
					newall = append(newall, extraComment)
					plen := 0
					classProjectName := strings.TrimPrefix(fsutil.Realpath(file.Package.NameInProject+"/"+recvName), "/")
					classRefPkgMap := importUsage[classProjectName]
					if classRefPkgMap == nil {
						classRefPkgMap = map[string]string{}
						importUsage[classProjectName] = classRefPkgMap
					}
					if realNode.Type != nil && realNode.Type.Params != nil {
						for _, field := range realNode.Type.Params.List {
							plen += len(field.Names)
							info := parse.GetFieldTypeName(field.Type)
							classRefPkgMap[info.TypeName] = info.NameSpace
						}
					}
					if realNode.Type != nil && realNode.Type.Results != nil {
						for _, field := range realNode.Type.Results.List {
							info := parse.GetFieldTypeName(field.Type)
							classRefPkgMap[info.TypeName] = info.NameSpace
						}
					}
					if len(recvName) != 0 {
						plen++
					}
				}
				realNode.Decorations().Start = newall
				realNode.Decs.Before = dst.NewLine

			}
		case *dst.GenDecl:
			//查看是否有无参数的New函数，没有就添加
			for _, spec := range realNode.Specs {
				switch realSpec := spec.(type) {
				case *dst.TypeSpec:
					{
						expr := realSpec.Type
						_, ok := expr.(*dst.StructType)
						if !ok {
							break
						}
						className := realSpec.Name.Name
						checkAddConstructorFunc(className, file)
						checkAddGetRawTypeFunc(className, file)
					}

				}
			}
		}
		return true
	}, nil).(*dst.File)
	restoredFset, restoredFile, err := decorator.RestoreFile(file.DSTFile)
	if err != nil {
		log.Fatal(err)
	}
	savePath := *goFileRootPath + file.ProjectPath
	savePath = fsutil.Realpath(savePath)
	parent, _ := fsutil.SplitPath(savePath)
	os.MkdirAll(parent, os.ModePerm)
	newFile, er := os.OpenFile(savePath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
	if er != nil {
		log.Fatalln(er)
	}

	if err := format.Node(newFile, restoredFset, restoredFile); err != nil {
		log.Fatal(err)
	}
	importMap := map[string]string{}
	for _, spec := range file.DSTFile.Imports {
		splits := strings.Split(spec.Path.Value, "/")
		importName := strings.ReplaceAll(splits[len(splits)-1], "\"", "")
		if spec.Name != nil {
			importName = spec.Name.Name
		}
		importPath := strings.ReplaceAll(spec.Path.Value, "\"", "")
		importMap[importName] = importPath
	}
	for _, refMap := range importUsage {
		for k, v := range refMap {
			realPath, ex := importMap[v]
			if ex {
				refMap[k] = realPath
			}
			if v == "" {
				refMap[k] = file.Package.GlobalName
			}
		}
	}
}

func checkAddGetRawTypeFunc(className string, file *parse.File) {
	funcName := "InternalRawType"
	//pre, preex := file.Package.Classes[className].Functions["internalRawType"]
	//if preex {
	//	pre.Name.Name = funcName
	//	file.Package.Classes[className].Functions[funcName] = pre
	//	return
	//}
	_, fex := file.Package.Classes[className].Functions[funcName]
	if fex {
		return
	}

	parse.AddImport(file.DSTFile, "reflect")
	inter := createSelectExpAsFun(createCallExp("reflect", "ValueOf", []dst.Expr{dst.NewIdent("x")}), "Type", nil)

	funcDeclare := &dst.FuncDecl{
		Recv: &dst.FieldList{
			Opening: true,
			List: []*dst.Field{
				{
					Names: []*dst.Ident{dst.NewIdent("x")},
					Type: &dst.StarExpr{
						X:    dst.NewIdent(className),
						Decs: dst.StarExprDecorations{},
					},
				},
			},
			Closing: true,
		},
		Name: &dst.Ident{
			Name: funcName,
			Obj:  nil,
			Path: "",
		},
		Body: &dst.BlockStmt{
			List: []dst.Stmt{
				&dst.ReturnStmt{Results: []dst.Expr{
					createSelectExpAsFun(createCallExp("reflect", "ValueOf", []dst.Expr{inter}), "Uint", nil),
				}},
			},
		},
		Type: &dst.FuncType{
			Func: true,
			Params: &dst.FieldList{
				Opening: true,
				List:    []*dst.Field{},
				Closing: true,
			},
			Results: &dst.FieldList{
				Opening: true,
				List: []*dst.Field{{
					Type: dst.NewIdent("uint64"),
				}},
				Closing: true,
			},
			Decs: dst.FuncTypeDecorations{},
		},
	}
	file.Package.Classes[className].Functions[funcName] = funcDeclare
	file.DSTFile.Decls = append(file.DSTFile.Decls, funcDeclare)
	file.Imports["\"reflect\""] = "\"reflect\""
}

func checkAddConstructorFunc(className string, file *parse.File) {
	ConstructorName := fmt.Sprintf("%s_New", className)
	_, fex := file.Package.Functions[ConstructorName]
	if fex {
		return
	}
	funcDeclare := &dst.FuncDecl{
		Name: &dst.Ident{
			Name: ConstructorName,
			Obj:  nil,
			Path: "",
		},
		Body: &dst.BlockStmt{
			List: []dst.Stmt{
				&dst.ReturnStmt{
					Results: []dst.Expr{
						&dst.CallExpr{
							Fun:  dst.NewIdent("new"),
							Args: []dst.Expr{dst.NewIdent(className)},
						},
					}},
			},
		},
		Type: &dst.FuncType{
			Func: true,
			Params: &dst.FieldList{
				Opening: true,
				List:    []*dst.Field{},
				Closing: true,
			},
			Results: &dst.FieldList{
				Opening: true,
				List: []*dst.Field{{
					Names: nil,
					Type: &dst.StarExpr{
						X: dst.NewIdent(className),
					},
				}},
				Closing: true,
			},
			Decs: dst.FuncTypeDecorations{},
		},
	}

	file.Package.Functions[ConstructorName] = funcDeclare
	file.DSTFile.Decls = append(file.DSTFile.Decls, funcDeclare)
	//g.Log().Infof("%s %s %+v", file.FullPath, ConstructorName, funcDeclare)
}

func createCallExp(pkg string, funName string, exps []dst.Expr) *dst.CallExpr {
	var fun dst.Expr
	if pkg == "" {
		fun = dst.NewIdent(funName)
	} else {
		fun = &dst.SelectorExpr{
			X:   dst.NewIdent(pkg),
			Sel: dst.NewIdent(funName),
		}
	}
	return &dst.CallExpr{
		Fun:  fun,
		Args: exps,
	}
}

func createSelectExp(from dst.Expr, selected string) *dst.SelectorExpr {
	return &dst.SelectorExpr{
		X:    from,
		Sel:  dst.NewIdent(selected),
		Decs: dst.SelectorExprDecorations{},
	}
}

func createSelectExpAsFun(from dst.Expr, selected string, args []dst.Expr) *dst.CallExpr {
	return &dst.CallExpr{
		Fun:  createSelectExp(from, selected),
		Args: args,
	}
}
